import * as types from './effect-types';
import { CANCEL } from './symbol';
import { isArray, isGenerator } from './helper';
// 执行generator函数,需要提供这些环境
// 1. 需要generate函数
// 2. 需要提供环境 dispatch, getState和take监听的订阅触发机制
export default function runSaga(env, rootSaga, cb) {
    let subTasks = []; // 存放所有的子fork, 当父进程取消时，子进程也要跟着取消
    let task = {cancel: () => next(CANCEL)}
    const { getState, dispatch, takeChannel } = env;
    const it = isGenerator(rootSaga) ? rootSaga : rootSaga(); // 判断是generate函数还是生成器
    if (!it.next && !isGenerator(rootSaga)) {
        // 如果it没有next,并且不是一个生成器 那么是一个普通函数
        return it(); // 直接执行即可
    }

    // 迭代generator函数
    function next(val, isError) {
        if (!it.next) {
            return;
        }
        let res;
        // 根据不同的情况进行处理
        if (isError) {
            // cps报错了,直接报错即可
            res = it.throw(val);
        }
        else if (val === CANCEL) {
            subTasks.forEach(cancel => cancel());
            res = it.return(val);
        } else {
            res = it.next(val);
        }
        const { value, done } = res;
        // 如果yield的结果是generator的执行结果,调用runSaga执行
        if (isGenerator(value)) {
            runSaga(env, value);
        }
        if (!done) {
            if (value instanceof Promise) {
                value.then(next); // 等待promsie被resolve
            } else {
                switch (value.type) { // 根据yield出来的值进行不同的处理
                    case types.TAKE:
                        // 相当于eventEmitter的on 等待被触发
                        // 触发由中间件处理 putChannel
                        takeChannel(value.actionType, next);
                        break;
                    case types.PUT:
                        // 派发，并且不影响当前的主进程
                        dispatch(value.action)
                        next();
                        break;
                    case types.FORK:
                        // let a = runSaga(env, value.saga);
                        // 不会阻塞当前进程执行
                        // let obj = { cancel: a }
                        const task = runSaga(env, value.saga);
                        subTasks.push(task);
                        next(task);
                        break;
                    case types.CALL:
                        const { fn, args } = value;
                        if (fn instanceof Promise) {
                            // fn是promise实例
                            fn(...args).then(next);
                        } else {
                            // fn可能是一个普通函数,也可能是一个generate函数
                            runSaga(env, fn);
                            // 不阻塞
                            next();
                        }
                        break;
                    case types.CPS:
                        const { fn: cps, args: cpsArg } = value;
                        cps(...cpsArg, function (err, ...cpsArg) {
                            if (err) {
                                next(err, true); // 有错误
                            } else {
                                next(...cpsArg);
                            }
                        })
                        break;
                    case types.ALL:
                        const { genArrs } = value;
                        if (!isArray(genArrs)) return next();
                        const rets = [];
                        let index = 0;
                        let len = genArrs.length;
                        // 默认会阻塞当前进程
                        genArrs.forEach((item, i) => {
                            // 传入回调
                            let sTask = runSaga(env, item, function (v) {
                                index++;
                                rets[i] = v;
                                // 当长度相等时,就执行
                                if (index === len) {
                                    next(rets)
                                }
                            });
                            // 取消的时候全部都取消掉
                            subTasks.push(sTask);
                        })
                        break;
                    case types.CANCEL:
                        // 当前进程取消掉这个runSaga子进程，跟这个子进程相关的其子进程也会被取消掉
                        value.task.cancel(); // 取消即可，这里会派发一个next的动作，已经在next函数中处理了

                        // 当前进程继续向下执行
                        next();
                        break;
                }
            }
        } else {
            // 已经完成了 返回value的值
            cb && cb(value);
        }

    }
    // 默认先调用一次
    next();
    return task;
}